#-*- coding: utf-8 -*-
import os
import sys
import shutil
import sqlite3
import logging
import traceback
import collections

from selenose.excepts import DjangoError
from selenose.consts import LOGGER, DEFINE_PORT_ON_STARTUP, DJANGO_START_DELAY
from selenose.tools import Starter, kill, django_admin, get_available_port, create

# Get a logger
logger = logging.getLogger('%s.%s' % (LOGGER, __name__))

class Django(object):
    
    def __init__(self, did, settings, host, port, work, dumpdata_excludes):
        self.did = did
        self.settings = settings
        self.host = host
        self.port = port
        self.work = work
        self.dumpdata_excludes = dumpdata_excludes
        self.bn = 'selenose_%s' % self.did
        self.override = os.path.join(self.work, '%s.py' % self.bn)
        self.databases = {}
        self.snapshots = {}
        from selenose import settings
        for alias in settings.DATABASES:
            self.databases[alias] = os.path.join(self.work, '%s_%s.dbs' % (self.bn, alias))
            self.snapshots[alias] = os.path.join(self.work, '%s_%s.json' % (self.bn, alias))
        self.process = None
        self.current = None
        self.log = None

    def configure(self):
        content = '''
from %(settings)s import *
DATABASES = {}
%(databases)s
EMAIL_BACKEND = 'selenose.mail.EmailBackend'
INSTALLED_APPS += ('selenose', )
''' % { 'settings' : self.settings,
        'databases': ''.join(["""
DATABASES[%(alias)s] = {}
DATABASES[%(alias)s]['ENGINE'] = 'django.db.backends.sqlite3'
DATABASES[%(alias)s]['NAME']   = %(database)s
""" % {'alias': repr(alias), 'database': repr(database), } for alias, database in self.databases.items() ]), }
        fd = open(self.override, 'w')
        fd.write(content)
        fd.close()

    def syncdb_sync(self):
        for sub in self.syncdb_async():
            sub.wait()
    
    def syncdb_async(self):
        all = []
        for alias in self.databases:
            logger.info('synchronize %s %s database' % (self, alias))
            all.append(django_admin('syncdb', self.bn, self.work, self.getlog(), None, '--noinput', '--database=%s' % alias))
        return all

    def migrate_sync(self):
        for sub in self.migrate_async():
            sub.wait()
    
    def migrate_async(self):
        all = []
        for alias in self.databases:
            logger.info('migrate %s %s database' % (self, alias))
            all.append(django_admin('migrate', self.bn, self.work, self.getlog(), None, '--database=%s' % alias))
        return all

    def trucate(self):
        for alias in self.databases:
            logger.info('truncate %s %s database' % (self, alias))
            django_admin('truncate', self.bn, self.work, self.getlog(), None, '--noinput', '--database=%s' % alias).wait()

    def restore(self):
        for alias in self.databases:
            logger.info('restore %s %s database' % (self, alias))
            django_admin('restore', self.bn, self.work, self.getlog(), None, '--database=%s' % alias, self.snapshots[alias]).wait()

    def loaddata(self, fixtures):
        if len(fixtures) != 0:
            for alias in self.databases:
                logger.info('load %s %s database with %s' % (self, alias, ', '.join(fixtures)))
                django_admin('loaddata', self.bn, self.work, self.getlog(), None, '--database=%s' % alias, *fixtures).wait()

    def getlog(self):
        if self.log is None:
            self.log = open(os.path.join(self.work, '%s.log' % self.bn), 'a')
        return self.log

    def snapshot(self):
        for alias in self.snapshots:
            fd = open(self.snapshots[alias], 'w')
            django_admin('dumpdata', self.bn, self.work, fd, self.getlog(), '--indent=4', '--format=json', '--database=%s' % alias, *[ '--exclude=%s' % e for e in self.dumpdata_excludes ] ).wait()
            fd.close()
    
    def flashback(self):
        self.trucate()
        self.restore()

    def update(self):
        for database in self.databases.values():
            conn = sqlite3.connect(database)
            cursor = conn.cursor()
            cursor.execute('select name from sqlite_master where name="django_site"')
            if len(cursor.fetchall()) != 0:
                cursor.execute('update django_site set domain = ?', ('%s:%s' % (self.host, self.getport()), ))
            conn.commit()
            cursor.close()
            conn.close()

    def start(self):
        if self.port == DEFINE_PORT_ON_STARTUP:
            self.current = get_available_port()
        else: self.current = self.port
        self.update()
        logger.info('start %s' % self)
        starter = Starter(self.getlog())
        self.process = django_admin('runserver', self.bn, self.work, self.getlog(), None, '%s:%d' % (self.host, self.current), '--noreload')
        try: starter.wait((sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C', DJANGO_START_DELAY)
        except BaseException, e:
            self.stop()
            raise e
    
    def stop(self):
        if self.process is not None:
            logger.info('stop %s' % self)
            kill(self.process)
            self.process = None
    
    def clean(self):
        if self.log is not None:
            self.log.close()
        if os.path.isdir(self.work):
            for fn in os.listdir(self.work):
                if fn.startswith(self.bn):
                    os.remove(os.path.join(self.work, fn))
    
    def geturl(self):
        return 'http://%s:%s' % (self.host, self.getport())
    
    def getport(self):
        if self.current is not None: 
            return self.current
        elif self.port != DEFINE_PORT_ON_STARTUP:
            return self.port
        else:
            return None
    
    def __str__(self):
        return 'Django(%s, %s:%s)' % (self.did, self.host, {True: '?', False: str(self.getport()), }[self.getport() is None])

class Djangos(object):
    '''Take care of the Grid'''
    
    def __init__(self, settings, host, nbrs, work, dumpdata_excludes):
        self.settings = settings
        self.host = host
        self.work = os.path.abspath(os.path.normpath(work))
        self.nbrs = nbrs
        self.djangos = {}
        self.delete = False
        for did in range(self.nbrs):
            djan = Django(did, self.settings, host, DEFINE_PORT_ON_STARTUP, self.work, dumpdata_excludes)
            self.djangos[did] = djan
        self.queue = collections.deque(range(self.nbrs))
            
    def initialize(self):
        self.clean()
        self.delete = create(self.work)
        try:
            syncs = []
            for djan in self.djangos.values():
                djan.configure()
                syncs.extend(djan.syncdb_async())
            for sync in syncs:
                sync.wait()
            from selenose import settings
            if 'south' in settings.INSTALLED_APPS:
                migrates = []
                for djan in self.djangos.values():
                    migrates.extend(djan.migrate_async())
                for migrate in migrates:
                    migrate.wait()
        except BaseException:
            logger.error(traceback.format_exc())
            logger.error('failed to initialize %s' % djan)
            self.clean()
            raise DjangoError('failed to initialize all servers')

    def snapshot(self):
        try:
            for djan in self.djangos.values():
                djan.snapshot()
        except BaseException:
            logger.error(traceback.format_exc())
            logger.error('failed to snapshot %s' % djan)
            raise DjangoError('failed to snapshot all servers')

    def put(self, djan):
        logger.info('%s back in pool' % djan)
        self.queue.append(djan.did)
    
    def get(self):
        if len(self.queue) == 0:
            raise DjangoError('no server available')
        did = self.queue.popleft()
        djan = self.getid(did)
        logger.info('got %s for test' % djan)
        return djan
    
    def getid(self, did):
        return self.djangos.get(did)

    def clean(self):
        if os.path.isdir(self.work):
            if self.delete:
                shutil.rmtree(self.work)
            else:
                error = False
                for djan in self.djangos.values():
                    try:
                        djan.clean()
                    except BaseException:
                        logger.error(traceback.format_exc())
                        logger.error('failed to clean %s' % djan)
                        error = True
                if error: raise DjangoError('failed to clean all servers')

